# coding=utf-8
# encoding:utf-8
import calendar
import datetime
import numpy as np
import pandas as pd
from pyspark.sql import Row


class Constant:

    def __init__(self):
        pass

    time_interval_week = "week"
    time_interval_month = "month"


class Util:

    def __init__(self):
        pass

    quarter_month = {1: 1, 2: 1, 3: 1, 4: 2, 5: 2, 6: 2, 7: 3, 8: 3, 9: 3, 10: 4, 11: 4, 12: 4}
    month_quarter = {1: [1, 2, 3], 2: [4, 5, 6], 3: [7, 8, 9], 4: [10, 11, 12]}
    date_format = "%Y-%m-%d"

    @staticmethod
    def get_dates(arr):
        if arr is None or len(arr) == 0:
            return None
        dts = [Util.get_date(d) for d in arr]
        return dts

    @staticmethod
    def get_date(str_datetime):
        # type: (str) -> datetime
        result = "1970-01-01"
        if str_datetime is not None and len(str_datetime) > 0 and str_datetime != "null":
            str_datetime = str_datetime[:len(result)]
            result = str_datetime.replace("00:00:00.0", "").replace("00:00:00", "").strip()
        return datetime.datetime.strptime(result, Util.date_format)

    @staticmethod
    def generate_df_from_rows(rows):
        row_list = list()
        for row in rows:
            row_list.append(row.asDict())
        df = pd.DataFrame(row_list)
        return df

    @staticmethod
    def generate_rows_from_df(df, cast_int=None):
        columns = df.columns.tolist()
        row_list = list()
        for row in df.iterrows():
            k_v = dict()
            for key in columns:
                if cast_int is not None and key in cast_int:
                    k_v[key] = int(row[1][key])
                else:
                    k_v[key] = row[1][key]
            row_list.append(Row(**k_v))
        return row_list

    @staticmethod
    def get_seasonality_length(time_interval):
        if time_interval == Constant.time_interval_week:
            return 52
        elif time_interval == Constant.time_interval_month:
            return 12
        else:
            return 4

    @staticmethod
    def get_week_index_in_quarter(year, month, day):
        quarter = Util.get_quarter(month)
        ms = Util.get_month_from_quarter(quarter)
        week_of_quarter = Util.get_week_index_in_month(day)
        if month > ms[0]:
            week_of_quarter += Util.get_week_in_month_cycle(year=year, month=ms[0])
        if month > ms[1]:
            week_of_quarter += Util.get_week_in_month_cycle(year=year, month=ms[1])
        return week_of_quarter

    @staticmethod
    def get_week_in_month_cycle(date=None, year=None, month=None):
        year = date.year if year is None else year
        month = date.month if month is None else month
        c = calendar.Calendar()
        mc = c.monthdayscalendar(year, month)
        cycle = len(mc) if mc[0][0] == 1 else len(mc) - 1
        return cycle

    @staticmethod
    def get_week_index_in_month(day):
        return (day - 1) / 7 + 1

    @staticmethod
    def get_week_index_of_year(date):
        dt = Util.get_monday(date)
        # date should be Monday
        week_idx = int(dt.strftime('%W')) - 1
        return week_idx

    @staticmethod
    def get_quarter(month):
        return Util.quarter_month[month]

    @staticmethod
    def get_month_from_quarter(quarter):
        return Util.month_quarter[quarter]

    @staticmethod
    def get_monday(date):
        days = date.weekday()
        return date - datetime.timedelta(days=days)

    @staticmethod
    def normalize(arr):
        tmp_arr = np.asarray(arr)
        result = tmp_arr / tmp_arr.mean()
        result = np.asarray([0.0 if np.isinf(p) or np.isnan(p) else p for p in result])
        return result


class Seasonality:

    def __init__(self, time_interval=Constant.time_interval_week, seasonality_interval=Constant.time_interval_week):
        self.name = "LifeCycle"
        self.level_factor = 0.0
        self.time_interval = time_interval
        self.seasonality_interval = seasonality_interval
        self.seasonality_length = self.get_seasonality_length()
        self.seasonality = np.ones(self.seasonality_length)
        # true seasonality before whole year normalization
        self.actual_seasonality = list()
        # true seasonality after whole year normalization
        self.real_seasonality = list()
        self.alpha = 0.5
        self.beta = 0.4
        self.last_idx = None
        print(self.seasonality, "self.seasonality ---- Create at constructor --- self.seasonality_length", self.seasonality_length)
        print("seasonality_interval -- input from Class Creator(default week):", seasonality_interval)
        # exit(0)

    def get_seasonality_length(self):
        return Util.get_seasonality_length(self.seasonality_interval)

    def get_idx(self, date):
        if self.seasonality_interval == Constant.time_interval_week:
            idx = Util.get_week_index_of_year(date)
        elif self.seasonality_interval == Constant.time_interval_month:
            idx = date.month - 1
        else:
            idx = Util.get_quarter(date.month) - 1
        return idx

    def update_seasonality(self, idx_o, qty):
        print(" ===========================update_seasonality(self, idx_o, qty)========================")

        idx = idx_o % self.seasonality_length  # Seasonality Length: 52, 12, 4
        print('&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&idx:', idx, 'idx_o:', idx_o, 'sum(qty):', qty,)
        # print(self.seasonality,)
        print('self.seasonality_length', self.seasonality_length)
        print('self.seasonality-----------\n', self.seasonality)
        print('self.real_seasonality------\n', self.real_seasonality)
        if idx == 0 and self.last_idx != 0 and len(self.actual_seasonality) != 0:

            self.real_seasonality = np.concatenate((self.real_seasonality, self.seasonality))
            print("np.concatenate((self.real_seasonality, self.seasonality))---\n", self.real_seasonality)
        print("before calculate -- level_"+str(idx), self.level_factor, 'quantity_'+str(idx), qty, 'seasonality_'+str(idx), self.seasonality[idx])
        self.level_factor = self.alpha * qty + (1 - self.alpha) * self.level_factor
        print(" after calculate -- level_"+str(idx), self.level_factor, 'quantity_'+str(idx), qty, 'seasonality_'+str(idx), self.seasonality[idx])

        if self.level_factor == 0.0:
            self.seasonality[idx] = self.seasonality[idx]
        else:
            self.seasonality[idx] = self.beta * qty / self.level_factor + (1 - self.beta) * self.seasonality[idx]
        self.level_factor /= np.mean(self.seasonality)
        # print(np.mean(self.seasonality), self.seasonality)
        self.seasonality = Util.normalize(self.seasonality)
        # print('Util.normalize(self.seasonality) -----\n', self.seasonality)
        if self.seasonality_interval == Constant.time_interval_week:
            # print("!!!self.seasonality_interval == Constant.time_interval_week", self.seasonality_interval, Constant.time_interval_week)
            self.actual_seasonality.append(self.seasonality[idx])
            print('self.actual_seasonality ------\n', np.array(self.actual_seasonality))
        else:
            if idx == self.last_idx:
                self.actual_seasonality[-1] = self.seasonality[idx]
            else:
                self.actual_seasonality.append(self.seasonality[idx])
        self.last_idx = idx
        # if there's 53 weeks in a year, real seasonality should append the 53'th week's seasonality
        # because it only append 52-week seasonality at the end of a year
        print('self.real_seasonality ==============before========\n', self.real_seasonality)
        if idx_o == self.seasonality_length:
            self.real_seasonality = np.append(self.real_seasonality, self.seasonality[idx])
            print('self.real_seasonality ------------', self.real_seasonality)

    def generate_seasonality(self, df, dt=None):
        print(dt)
        # dt = Util.get_monday(datetime.datetime.now()) if dt is None else dt
        # todo:需要补全到预测点的日期和数据
        # df = Util.fill_missing_date(df, Util.getDateStr(dt), self.time_interval)
        df.fillna(0, inplace=True)
        # print("before add_date_feature_according_to_time_interval\n", df)
        df = self.add_date_feature_according_to_time_interval(df)
        print("========after=========\n", df)
        # df.to_csv('feature_2nd_level.csv', header=True)
        idx_first = (int(df.get_value(0, "2nd_date_level")) - 1) % self.seasonality_length

        for row in df.iterrows():
            # print('row --------', type(row[1]), '\n-------\n', row[1], '\n')
            self.update_seasonality(int(row[1]["2nd_date_level"])-1, row[1]['sum'])

        print(type(self.update_seasonality), '\n------self.update_seasonality--------\n', self.update_seasonality)

        self.real_seasonality = np.concatenate((self.real_seasonality, self.seasonality))
        self.real_seasonality = self.real_seasonality[idx_first:]
        # print(df)
        df = self.add_seasonality(df)
        print("--------------------")
        # print(df)

        return df

    def add_date_feature_according_to_time_interval(self, df):
        dts = Util.get_dates(df['date'].tolist())
        df['year'] = [dt.year for dt in dts]
        if self.seasonality_interval == Constant.time_interval_week:
            week_in_year = [self.get_idx(dt) + 1 for dt in dts]
            df["2nd_date_level"] = week_in_year
            return df
        if self.seasonality_interval == Constant.time_interval_month:
            df["2nd_date_level"] = [dt.month for dt in dts]
        else:
            df["2nd_date_level"] = [Util.get_quarter(dt.month) for dt in dts]
        return df

    def add_seasonality(self, df):
        if self.seasonality_interval == Constant.time_interval_week:
            self.real_seasonality = self.real_seasonality[:len(df)]
            df['seasonality'] = self.actual_seasonality
            df["real_seasonality"] = self.real_seasonality
        else:
            tmp_df_seasonality = df.groupby(['year', "2nd_date_level"])['sum'].\
                sum().reset_index().sort_values(by=['year', "2nd_date_level"]).reset_index(drop=True)
            del tmp_df_seasonality['sum']
            self.real_seasonality = self.real_seasonality[:len(tmp_df_seasonality)]
            tmp_df_seasonality['seasonality'] = self.actual_seasonality
            tmp_df_seasonality["real_seasonality"] = self.real_seasonality
            df = df.merge(tmp_df_seasonality, on=['year', "2nd_date_level"], how="left")
        del df['year']
        del df["2nd_date_level"]
        return df
